Please enable JavaScript to view this website.

Skip to main content

Phase 1: Bootstrap Certificate via REST API

Who should read these docs?

The engineer writing or integrating the end-of-line programming utility that runs at the manufacturing station. Firmware engineers also need this context to understand how the firmware needs to interact with the programming utility.

Start here

This page documents how to obtain a bootstrap certificate during manufacturing using the REST API. The bootstrap certificate is created from a device-generated Certificate Signing Request (CSR), ensuring that private keys never leave the device.

Prerequisites

Before attempting to create a Bootstrap Certificate for your device, ensure:

  1. A tool record exists with a unique MPBID for the device. Tool records are created via Manufacturing REST API (see Prerequisites & Manufacturing Setup)
  2. You have obtained Auth0 client credentials to access the Digital IoT Rest API

Contact the CPP Identity & Remote Comms team if you're unsure about these requirements. (Open a support ticket)

Workflow Overview

  1. Device generates an RSA key pair and CSR on-device
  2. CSR is passed to the programming utility app (via BLE or over the wire)
  3. Programming utility obtains a Bearer token from Auth0, then POSTs the CSR to the /certificate/csr endpoint
  4. API validates the MPBID format and verifies it exists in the tool records system
  5. API issues a signed bootstrap certificate from the private Milwaukee Tool Certificate Authority and seeds default device shadows in AWS IoT Core
  6. Programming utility stores the bootstrap certificate on the device

Certificate API Endpoint

API Spec

The endpoint used in this phase is POST /management/v1/devices/certificate/csr. Full request/response schema, error codes, and environment base URLs are documented in the API reference.

Request Payload

Full field descriptions and validation rules are in the API reference. Two fields have CPP-specific behavior worth calling out:

  • mpbid: Must be uppercase. The REST API normalizes the value, but the provisioning pre-hook Lambda (Phase 2) enforces strict uppercase and will reject requests containing lowercase characters. Always send uppercase across both phases.
  • deviceType: This value is embedded as the Common Name (CN) field in the issued certificate. It must match exactly what the backend expects for your product. Contact the CPP Identity & Remote Comms team for the correct value before manufacturing begins.

CSR Subject Fields

The device must generate a CSR containing these X.509 subject fields exactly. The Common Name and Given Name fields are device-specific; all others are fixed values.

FieldOIDValue
Common Name (CN)2.5.4.3Device type string (e.g., bridge). Contact the CPP team for your product's value.
Given Name (GN)2.5.4.42MPBID (10-character hex string) unique for each unit
Organization (O)2.5.4.10Milwaukee Tool
Organizational Unit (OU)2.5.4.11Connected Products
Country (C)2.5.4.6US
State (ST)2.5.4.8WI
Locality (L)2.5.4.7Brookfield
danger

The private key must never leave the device. Only the CSR (which contains the public key) should be transmitted. If the Organization field does not equal Milwaukee Tool exactly, the device will be able to connect via MQTT after provisioning but all publish and subscribe operations will be silently denied.

Reference Implementation

The following Python script shows the complete Phase 1 flow. Replace the constants at the top with values for your environment and device.

import requests
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import hashes, serialization
from cryptography import x509
from cryptography.x509.oid import NameOID

# --- Configuration ---
CLIENT_ID = "your_client_id" # Auth0 client ID (see Prerequisites)
CLIENT_SECRET = "your_client_secret" # Auth0 client secret (see Prerequisites)
DEVICE_MPBID = "FFFF000001" # 10-char uppercase hex MPBID of the device
DEVICE_TYPE = "bridge" # Contact CPP team for your product's value

# Select the API and Auth0 endpoints for the device's region of sale.
# See Prerequisites for the full endpoint reference.
REGION_CONFIG = {
"us": {"api_url": "https://api.prod.iot.digital.milwaukeetool.com",
"auth_url": "https://id.milwaukeetool.com/oauth/token",
"audience": "https://api.prod.iot.digital.milwaukeetool.com/"},
"eu": {"api_url": "https://api.prod.iot.digital.milwaukeetool.eu",
"auth_url": "https://id.milwaukeetool.com/oauth/token",
"audience": "https://api.prod.iot.digital.milwaukeetool.eu/"},
"apac": {"api_url": "https://api.prod.iot.digital.milwaukeetool.com.au",
"auth_url": "https://id.milwaukeetool.com/oauth/token",
"audience": "https://api.prod.iot.digital.milwaukeetool.com.au/"},
}

REGION = "us" # Set to "us", "eu", or "apac" based on the device's region of sale
config = REGION_CONFIG[REGION]

# --- Step 1: Get an Auth0 Bearer token ---
auth_response = requests.post(
config["auth_url"],
headers={"Accept": "application/json", "Content-Type": "application/json"},
json={
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
"audience": config["audience"],
"grant_type": "client_credentials",
},
timeout=20,
)
auth_response.raise_for_status()
bearer_token = auth_response.json()["access_token"]

# --- Step 2: Generate RSA key pair and CSR on the programming utility ---
# DEMO ONLY: In this script, key generation runs on the utility for simplicity.
# In production firmware, the RSA key pair MUST be generated on the device itself.
# The device sends only the CSR (containing the public key) to the utility over
# BLE or a wired interface. The private key must never leave the device.
# The private_key variable below is a stand-in; replace this block with a call
# to your device's on-device key generation API.
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)

csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, DEVICE_TYPE),
x509.NameAttribute(NameOID.GIVEN_NAME, DEVICE_MPBID),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Milwaukee Tool"),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME,"Connected Products"),
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, "WI"),
x509.NameAttribute(NameOID.LOCALITY_NAME, "Brookfield"),
])).sign(private_key, hashes.SHA256())

csr_pem = csr.public_bytes(serialization.Encoding.PEM).decode("utf-8")

# --- Step 3: Request the bootstrap certificate ---
cert_response = requests.post(
f"{config['api_url']}/management/v1/devices/certificate/csr",
headers={
"Authorization": f"Bearer {bearer_token}",
"Accept": "application/json",
"Content-Type": "application/json; charset=utf-8",
},
json={
"mpbid": DEVICE_MPBID,
"deviceType": DEVICE_TYPE,
"certificateSigningRequest": csr_pem,
},
timeout=20,
)
cert_response.raise_for_status()
result = cert_response.json()

# --- Step 4: Send certificate back to the device for storage ---
# In production: the utility transmits only certificate_pem back to the device
# (via the same BLE or wired interface used in Step 2). The device stores it
# alongside the private key that was already in secure storage on-device —
# the private key was generated on-device in Step 2 and never left the device.
#
# In this demo script: private_key was generated on the utility (see the DEMO
# ONLY block in Step 2), so private_key_pem can be serialized here. This does
# not reflect production. In a real integration, the utility never has the
# private key and this serialization step does not exist.
certificate_id = result["certificateId"]
certificate_pem = result["certificatePem"]
private_key_pem = private_key.private_bytes( # DEMO ONLY — does not exist in production
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
).decode("utf-8")

print(f"Certificate ID: {certificate_id}")
# Never log or transmit the private key.
#
# NOTE: certificate_id does NOT need to be stored on the device. It is an AWS
# internal identifier used by the platform; the device never needs to reference
# it. Only certificate_pem and private_key_pem are required on the device.

Common Error Scenarios

HTTP StatusCauseResolution
400Invalid MPBID formatEnsure MPBID is exactly 10 uppercase hexadecimal characters
400Malformed or missing request body fieldsInclude all required fields: mpbid, deviceType, certificateSigningRequest
400Malformed CSRVerify CSR is valid PEM format with correct subject fields
401Invalid or expired Bearer tokenRefresh the Auth0 access token
404MPBID not found in tool records systemCreate the tool record via the manufacturing API before requesting a certificate
500Tool records service or Certificate Authority errorRetry with exponential backoff; contact support if persistent